package csm.common.security;

import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

/**
 * 문자열에 해쉬 알고리즘을 적용하는 인코더 클래스 입니다.
 *
 * @author seungmun
 * @since 2012. 7. 19.
 * @version
 * @see<pre>
 *
 * </pre>
 */
public final class HashCryptography
{
    /**
     * 해쉬문자열을 가져옵니다.
     *
     * @param secret
     *            적용대상이 되는 문자열
     * @param salt
     *            해쉬키, 4byte ~ 8byte의 크기 범위에 있어야 합니다. (appendSaltToResult = true 인 경우는 필수 값)
     * @param algorithm
     *            해쉬알고리즘 종류
     *            <ul>
     *              <li>SHA1
     *              <li>MD5
     *              <li>SHA-256 (기본값)
     *              <li>SHA-384
     *              <li>SHA-512
     *            </ul>
     * @param charset
     *            secret와 salt를 바이트배열로 변환할때 사용하는 문자열 인코딩입니다. (기본값 : UTF-8)
     * @param appendSaltToResult
     *            반환방법
     *            <ul>
     *            <li><code>true</code> - 알고리즘이 적용된 해쉬 바이트 배열에 salt 바이트 배열을 합쳐서 문자열을 생성하여 반환합니다.
     *            <li><code>false</code> - 기본값, 알고리즘이 적용된 해쉬 바이트 배열만을 가지고 문자열을 생성하여 반환합니다.
     *            </ul>
     * @return 해쉬알고리즘으로 암호화된 결과가 다시 Base64로 인코딩된 문자열
     * @throws NoSuchAlgorithmException
     * @throws UnsupportedEncodingException
     */
    public static String getHash(String secret, String salt, String algorithm, String charset, boolean appendSaltToResult) throws NoSuchAlgorithmException, UnsupportedEncodingException
    {
        if (appendSaltToResult) {
            if (salt == null || salt.length() == 0 || salt.trim().length() == 0)
                throw new NullPointerException("salt value was null !!");
        }

        if (charset == null || charset.length() == 0 || charset.trim().length() == 0) charset = "UTF-8";

        if (algorithm == null || algorithm.length() == 0 || algorithm.trim().length() == 0) algorithm = HashAlgorithm.SHA256;

        byte[] secretBytes = secret.getBytes(charset);
        byte[] saltBytes = null;
        byte[] resultOfHashing = null;
        String hashedValue = "";

        MessageDigest digest = MessageDigest.getInstance(algorithm);

        if (salt != null && salt.length() > 0){
            saltBytes = salt.getBytes(charset);
            digest.update(secretBytes);
            resultOfHashing = digest.digest(saltBytes);
        }
        else
            resultOfHashing = digest.digest(secretBytes);

        if (appendSaltToResult)
        {
            byte[] buffer = new byte[resultOfHashing.length + saltBytes.length];

            for (int i = 0; i < resultOfHashing.length; i++)
                buffer[i] = resultOfHashing[i];

            for (int i = 0; i < saltBytes.length; i++)
                buffer[resultOfHashing.length + i] = saltBytes[i];

            hashedValue = Base64Encoder.encode(buffer);
        }
        else
            hashedValue = Base64Encoder.encode(resultOfHashing);


        return hashedValue;
    }

    /**
     * 해쉬문자열을 가져옵니다.
     *
     * @param secret
     *            적용대상이 되는 문자열
     * @param salt
     *            해쉬키, 4byte ~ 8byte의 크기 범위에 있어야 합니다.
     * @param algorithm
     *            해쉬알고리즘 종류
     *            <ul>
     *            <li>SHA1
     *            <li>MD5
     *            <li>SHA-256 (기본값)
     *            <li>SHA-384
     *            <li>SHA-512
     *            </ul>
     * @param charset
     *            secret와 salt를 바이트배열로 변환할때 사용하는 문자열 인코딩입니다. (기본값 : UTF-8)
     * @return 해쉬알고리즘으로 암호화된 결과가 다시 Base64로 인코딩된 문자열
     * @throws NoSuchAlgorithmException
     * @throws UnsupportedEncodingException
     */
    public static String getHash(String secret, String salt, String algorithm, String charset) throws NoSuchAlgorithmException, UnsupportedEncodingException
    {
        return getHash(secret, salt, algorithm, charset, false);
    }

    /**
     * 해쉬문자열을 가져옵니다. (UTF-8 인코딩 사용)
     *
     * @param secret
     *            적용대상이 되는 문자열
     * @param salt
     *            해쉬키, 4byte ~ 8byte의 크기 범위에 있어야 합니다.
     * @param algorithm
     *            해쉬알고리즘 종류
     *            <ul>
     *            <li>SHA1
     *            <li>MD5
     *            <li>SHA-256 (기본값)
     *            <li>SHA-384
     *            <li>SHA-512
     *            </ul>
     * @return 해쉬알고리즘으로 암호화된 결과가 다시 Base64로 인코딩된 문자열
     * @throws NoSuchAlgorithmException
     * @throws UnsupportedEncodingException
     */
    public static String getHash(String secret, String salt, String algorithm) throws NoSuchAlgorithmException, UnsupportedEncodingException
    {
        return getHash(secret, salt, algorithm, "UTF-8", false);
    }

    /**
     * 해쉬문자열을 가져옵니다. (UTF-8 인코딩 사용)
     *
     * @param secret
     *            적용대상이 되는 문자열
     * @param algorithm
     *            해쉬알고리즘 종류
     *            <ul>
     *            <li>SHA1
     *            <li>MD5
     *            <li>SHA-256 (기본값)
     *            <li>SHA-384
     *            <li>SHA-512
     *            </ul>
     * @return 해쉬알고리즘으로 암호화된 결과가 다시 Base64로 인코딩된 문자열
     * @throws NoSuchAlgorithmException
     * @throws UnsupportedEncodingException
     */
    public static String getHash(String secret, String algorithm) throws NoSuchAlgorithmException, UnsupportedEncodingException
    {
        return getHash(secret, null, algorithm, "UTF-8", false);
    }
    /**
     * 지정한 바이트를 HEX 값으로 변환합니다.
     *
     * @param b
     * @return
     */
    public static String toHexString(byte b)
    {
        return String.format("%02x", b & 0xFF);
    }



    /**
     * 비밀번호를 암호화하는 기능(복호화가 되면 안되므로 SHA-256 인코딩 방식 적용)
     *
     * @param String data 암호화할 비밀번호
     * @return String result 암호화된 비밀번호
     * @exception Exception
     */
    public static String encryptPassword(String data) throws Exception
    {

        if (data == null)
        {
            return "";
        }

        byte[] plainText = null; // 평문
        byte[] hashValue = null; // 해쉬값
        plainText = data.getBytes();

        MessageDigest md = MessageDigest.getInstance("SHA-256");
        hashValue = md.digest(plainText);

        /*
         * BASE64Encoder encoder = new BASE64Encoder(); return encoder.encode(hashValue);
         */
        return new String(Base64Encoder.encode(hashValue));
    }

	public static void main(String[] args)
	{
		String testSalt = "pw112233$";
		String testPassword = "hello";
		String encodedPassword = "";

		testSalt = "";

		try
		{
			System.out.println("SALT VALUE :" + testSalt);
			System.out.println("SECRET VALUE :" + testPassword);

			//encodedPassword = HashCryptography.getHash(testPassword, testSalt, HashAlgorithm.SHA256, "UTF-8", true);
			encodedPassword = HashCryptography.getHash(testPassword, testSalt, HashAlgorithm.SHA256);
			System.out.println("Hashed RESULT : [" + encodedPassword + "]");

			encodedPassword = HashCryptography.encryptPassword(testPassword);

			System.out.println("encryptPassword RESULT : [" + encodedPassword + "]");

		}
		catch (NoSuchAlgorithmException e)
		{
			e.printStackTrace();
		}
		catch (UnsupportedEncodingException e)
		{
			e.printStackTrace();
		}
        catch (Exception e)
        {
            // TODO 자동 생성된 catch 블록
            e.printStackTrace();
        }

	}
}